// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	"flag"
	"fmt"
	"mime"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"syscall"

	"chainmaker.org/chainmaker/smarteditor/common"
	"chainmaker.org/chainmaker/smarteditor/monitor"

	"chainmaker.org/chainmaker/smarteditor/conf"
	"chainmaker.org/chainmaker/smarteditor/container"
	"chainmaker.org/chainmaker/smarteditor/docker"
	"chainmaker.org/chainmaker/smarteditor/event"
	"chainmaker.org/chainmaker/smarteditor/handler"
	"chainmaker.org/chainmaker/smarteditor/i18n"
	"chainmaker.org/chainmaker/smarteditor/logger"
	"chainmaker.org/chainmaker/smarteditor/server"
	"chainmaker.org/chainmaker/smarteditor/session"
)

var log = logger.Get()

var monitorNameSpace = "smartEditor"

type smartEditor struct {
}

func main() {
	smartEditor := &smartEditor{}

	smartEditor.init()

	smartEditor.addExtendMime()

	smartEditor.startRpcServer()

	smartEditor.registerHandler()
	mServer := monitor.NewMonitorServer(monitorNameSpace, conf.Wide.PrometheusPort, conf.Wide.EnablePrometheus)
	handler.InitMonitorSubName(monitorNameSpace)
	mServer.Start(conf.Wide.EnablePrometheus)

	go smartEditor.handleSignal()

	smartEditor.runHttpServer()
}

// init before run server
func (se *smartEditor) init() {
	confPath := flag.String(common.ConfigName, common.DefaultConfigPath, "path of config")
	flag.Parse()

	log.Infof("[init] config path:%s", *confPath)

	i18n.Load()
	event.Load()
	conf.Load(*confPath)
	session.InitCookieStore() // 初始化cookiestore
	changeConfigIfNotUseDocker()

	if conf.Docker {
		docker.RemoveAllDockerContainer()
		session.InitFailedDockerContainerReap()
	}

	container.InitServerContainer()

	excludeFileExtensionList := strings.Split(conf.Wide.ExcludeFileExtension, "|")
	excludeDirectoryList := strings.Split(conf.Wide.ExcludeFileExtension, "|")
	conf.InitExclude(excludeDirectoryList, excludeFileExtensionList)

	conf.FixedTimeCheckEnv()
	session.FixedTimeSave()
	session.FixedTimeRelease()
	session.FixedTimeReport()

	common.InitUniqueHost(conf.Wide.ListenPort)
	log.Debug("host [" + runtime.Version() + ", " + runtime.GOOS + "_" + runtime.GOARCH + "]")
}

// addExtendMime initializes mime types.
// We can't get the mime types on some OS (such as Windows XP) by default, so initializes them here.
func (se *smartEditor) addExtendMime() {
	mime.AddExtensionType(".css", "text/css")
	mime.AddExtensionType(".js", "application/x-javascript")
	mime.AddExtensionType(".json", "application/json")
}

func (se *smartEditor) startRpcServer() {
	rpcServerAddress := container.SC.RpcServerAddress
	rpcServer, err := server.NewContractRpcServer(context.Background(), rpcServerAddress)
	if err != nil {
		log.Errorf("[startRpcServer] new rpc server fail. err:%s", err)
		os.Exit(-1)
	}

	err = rpcServer.Start()
	if err != nil {
		log.Errorf("[startRpcServer] run rpc server fail. err:%s", err)
		os.Exit(-1)
	}

	container.SC.RpcServer = rpcServer

	log.Debugf("[startRpcServer] run rpc server success. address:%s", rpcServerAddress)
}

func (se *smartEditor) registerHandler() {
	handler.RegisterHttpHandler()
}

func (se *smartEditor) runHttpServer() {
	log.Infof("[runHttpServer] smarteditor is running. listener server:[%s], public server:[%s]", conf.Wide.ListenServer, conf.Wide.PublicServer)
	err := http.ListenAndServe(fmt.Sprintf("%s:%d", conf.Wide.ListenIp, conf.Wide.ListenPort), nil)
	if err != nil {
		log.Errorf("[runHttpServer] server run fail. err:%v", err)
		os.Exit(-1)
	}
}

// handleSignal handles system signal for graceful shutdown.
func (se *smartEditor) handleSignal() {
	signalChannel := make(chan os.Signal, 3)
	signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
	s := <-signalChannel

	log.Debugf("[handleSignal] got signal [%s], start stop server", s)

	log.Debugf("[handleSignal] saved all online user, start ...")
	session.SaveOnlineUsers()
	log.Debugf("[handleSignal] saved all online user, end  ...")

	log.Debugf("[handleSignal] stop rpc server, start  ...")
	container.SC.RpcServer.Close()
	log.Debugf("[handleSignal] stop rpc server, end  ...")

	os.Exit(0)
}

func changeConfigIfNotUseDocker() {
	if !conf.Wide.UseDocker {
		conf.Docker = false
	}
}
